Cargamos las librerías que vamos a utilizar

library(tidyverse)
library(tidymodels)
library(modelr)
library(ISLR)
library(GGally)
library(pROC)
library(cowplot)
library(OneR)
library(rlang)
library(caret)
set.seed(1992)

La regresión logística es útil para problemas de predicción de clases. En este caso vamos a tratar de resolver el problema de predecir si una persona va defaultear su deuda de tarjeta de crédito en base a ciertos predictores.

Levantamos el conjunto de datos

Este conjunto de datos proviene de la librería ISLR (Introduction to Statistical Learning Using R) de James, Witten, Hastie y Tibshirani.

# cargamos los datos y observamos su estructura
default <- Default
glimpse(default)
Rows: 10,000
Columns: 4
$ default <fct> No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, No, …
$ student <fct> No, Yes, No, No, No, Yes, No, Yes, No, No, Yes, Yes, No, No, No, No, No, Yes, No, No, No, No, No, No, No, No, No…
$ balance <dbl> 729.5265, 817.1804, 1073.5492, 529.2506, 785.6559, 919.5885, 825.5133, 808.6675, 1161.0579, 0.0000, 0.0000, 1220…
$ income  <dbl> 44361.625, 12106.135, 31767.139, 35704.494, 38463.496, 7491.559, 24905.227, 17600.451, 37468.529, 29275.268, 218…

Tiene 4 variables:

  • default: La clase que queremos predecir.

  • student: Binaria que indica si la persona es estudiante.

  • balance: Balance promedio que le queda a la persona luego de sus pagos mensuales.

  • income: Ingreso de la persona.

Exploratorias

Analicemos la distribución de la clase.

default %>% 
  group_by(default) %>% 
  summarise(numero_casos=n())
`summarise()` ungrouping output (override with `.groups` argument)

Vemos que estamos trabajando con un problema de clasificación con un claro desbalance de clase.

Realizamos un gráfico exploratorio completo para ver el comportamiento y las relaciones entre las variables. El color rojo designa a quienes no defaultean y el azul a los que sí.

# graficamos con ggpairs coloreando por variable a predecir
g <- ggpairs(default, 1:4,  title = "Correlograma de variables",
        mapping = aes(colour= default)) + 
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) + 
  theme_bw()
# hacemos un loop para cambiar los colores del gráfico
for(i in 1:g$nrow) {
  for(j in 1:g$ncol){
    g[i,j] <- g[i,j] + 
      scale_fill_brewer(palette="Set1") +  
      scale_color_brewer(palette="Set1")
        }
}
g

¿Qué pueden decir de la relación entre balance y default?

¿Y entre income y balance?

¿Cuáles parecen ser buenas variables para discriminar entre quienes defaultean y quienes no?

Limpieza

Para modelizar va a ser necesario tener la variable default como numérica. Definimos la variable como {0,1} para los valores {“No”,“Yes”}

default <- default %>% 
  mutate(default = case_when(default == "No" ~ 0,
                             default == "Yes" ~ 1))
default

Planteo del Problema

Queremos estimar \(P(Default=Yes|X)=P(X)\) para cada individuo y a partir de ello poder definir un punto de corte para predecir quiénes son los que van a entrar en default.

Regresión lineal

En este caso estamos modelando la probabilidad de la siguiente manera:

\(P(X)= \beta_0 + \sum\limits_{j=1}^p \beta_j X\)

Veamos que tan bueno es el modelo lineal para esto, usando balance como predictor.

test_mco <- default %>% 
              lm(formula = default ~ balance, data = .) 
tdy = test_mco %>% tidy() 
tdy
test_mco %>% glance()

Ambos estimadores son significativos y el test de significatividad global del modelo también es significativo.

Veamos un gráfico de nuestro modelo.

Parece tener bastantes problemas para estimar la probabilidad de default de los individuos. Por ejemplo, vemos que hay varios individuos a los cuales les asigna una probabilidad negativa.

Regresión Logística

Para evitar estos problemas, usamos la función logística.

\(P(Y=1|X)= \frac{e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}{1+e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}\)

El lado derecho se llama expit

Esta función acota el resultado entre 0 y 1, lo cual es mucho más adecuado para modelar una probabilidad.

Luego de hacer algunas operaciones, podemos llegar a la expresión:

\(\log {\frac{P(x)}{1-P(x)}}= \beta_0 + \sum\limits_{j=1}^p \beta_j X\)

El lado izquierdo es el logaritmo de los odds y se llama logit.

Partición del dataset en train y test

Realizamos una partición una partición entre dataset de entrenamiento (70%) y testeo (30%) usando la función initial_split del paquete rsample de tidymodels.

# fijamos semilla
set.seed(44)
# Partición Train y Test, indicando proporción
train_test <- initial_split(default, prop = 0.7)
# armamos dataframe de testeo y entrenamiento
default <- training(train_test)
test <- testing(train_test)
# vemos el contenido
default %>%
  dim_desc() # 7000 filas
[1] "[7,000 x 4]"
test %>%
  dim_desc() # 3000 filas
[1] "[3,000 x 4]"

Creación de fórmulas

Para aplicar la regresión logística primero usamos la función formulas del paquete modelr para crear un objeto que contiene todas las fórmulas que vamos a utilizar. En .response especificamos la variable respuesta de nuestras fórmulas y luego nombramos las fórmulas que queramos armar.

Así, armaremos distintos modelos combinando distintas variables.

logit_formulas <- formulas(.response = ~ default, # único lado derecho de las formulas.
                         bal = ~ balance, 
                         stud = ~ student,  
                         inc = ~ income,  
                         bal_stud = ~ balance + student, 
                         bal_inc = ~ balance + income, 
                         stud_inc = ~ student + income,  
                         full = ~ balance + income + student  
                         )
logit_formulas # observamos el objeto formulas
$bal
default ~ balance

$stud
default ~ student

$inc
default ~ income

$bal_stud
default ~ balance + student

$bal_inc
default ~ balance + income

$stud_inc
default ~ student + income

$full
default ~ balance + income + student

Creación de modelos

Procedemos a crear los modelos a partir de estas fórmulas.

models <- data_frame(logit_formulas) %>% # dataframe a partir del objeto formulas
  mutate(models = names(logit_formulas), # columna con los nombres de las formulas
         expression = paste(logit_formulas), # columna con las expresiones de las formulas
         mod = map(logit_formulas, ~glm(., family = 'binomial', data = default))) # Que estamos haciendo acá? Que vamos a encontrar en la columna?
models

La funcíón glm() nos permite crear un modelo lineal generalizado (Generalized Linear Model). Al igual que la función lm() toma como argumentos una formula y los datos pero también se debe especificar el argumento family: indicamos la distribución del error y la función link que vamos a utilizar en el modelo.

Algunas familias son:

  • Binomial: link=logit

  • Poisson: link=log

  • Gaussiana: link=identidad

Como estamos trabajando con un fenómeno que suponemos tiene una distribución binomial, así lo especificamos en el parámetro family.

Modelos simples

Probamos los primeros tres modelos, aquellos que tienen un único predictor. Usamos la función tidy para obtener los parámetros estimados para estos tres modelos.

models %>% 
  filter(models %in% c('bal','stud','inc')) %>%
  mutate(tidy = map(mod, tidy))  # Qué realizamos en este paso? Que va a tener esta columna?

Para acceder a los elementos de la nueva columna tidy debemos desanidarla.

models %>% 
  filter(models %in% c('bal','stud','inc')) %>%
  mutate(tidy = map(mod, tidy)) %>%  # Qué realizamos en este paso? Que va a tener esta columna?
  unnest(tidy) %>% 
  mutate(estimate=round(estimate,5), # redondeamos valores para facilitar lectura
         p.value=round(p.value,4))

Observamos que todos los modelos tienen coeficientes significativos excepto el coeficiente asociado al Ingreso (income), el cual tiene un p-valor de 0.17.

Interpretación de los coeficientes

Recordando la ecuación para modelar la probabilidad:

\(P(Y=1|X)= \frac{e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}{1+e^{\beta_0 + \sum\limits_{j=1}^p \beta_j X}}\)

Se observa que ahora las variables ya no tienen una relación lineal con la probabilidad. En este modelo un coeficiente positivo indica que frente a aumentos de dicha variable la probabilidad aumenta, mientras que un coeficiente negativo nos indica lo contrario. Para nuestro caso:

  • El coeficiente de 0.0056 de la variable balance indica que cuando el balance aumenta la probabilidad de default aumenta.

  • El coeficiente de 0.40886 de la variable studentYes indica que cuando una persona es estudiante su probabilidad de default aumenta respecto a una persona que no lo es.

  • El coeficiente de -0.00001 de la variable income indica que cuando el ingreso aumenta la probabilidad de default disminuye.

Recomendamos leer el capítulo de regresión logística de Interpretable Machine Learning: A Guide for Making Black Box Models Explainable de Molnar Cristoph para una discusión más profunda de la interpretación de un modelo de regresión logística.

Modelo completo

Ahora probamos con un modelo que utiliza las tres predictoras.

models %>% 
  filter(models == "full") %>%
  mutate(tidy = map(mod,tidy)) %>%
  unnest(tidy) %>% 
  mutate(estimate=round(estimate,5),
         p.value=round(p.value,4))

En el modelo con las tres variables los coeficientes de studentYes e income han cambiado de signo, por lo cual su efecto sobre la probabilidad de default es inverso al que observamos en los modelos simples. También hay que agregar ahora que el efecto de la variable se observa al estar controlando por las dos covariables restantes.

Asimismo, se observa que el coeficiente de income no resulta significativo tampoco en este modelo (p-valor>0.05).

Evaluación de todos los modelos

Con map() agregamos la función glance para traer información relevante para la evaluación del modelo. Con unnest() accedemos a dicha información. Por último, agregamos una columna con el porcentaje de deviance explicado por cada modelo y ordenamos el dataset según su valor de deviance.

# Calcular las medidas de evaluación para cada modelo
models <- models %>% 
  mutate(glance = map(mod,glance))
# Obtener las medidas de evaluacion de interes
models %>% 
  unnest(glance) %>%
  # Calculamos la deviance explicada
  mutate(perc_explained_dev = 1-deviance/null.deviance) %>% 
  select(-c(models, df.null, AIC, BIC)) %>% 
  arrange(deviance)

El modelo que utiliza las 3 variables es el que minimiza el deviance, aunque el modelo que excluye a la variable income (poco significativa) está muy cerca. Los 3 últimos modelos reducen muy poco el deviance respecto a la deviance nula.

Gráficos de Evaluación

Realizamos los gráficos para el modelo completo y uno de los modelos con mayor deviance (student+income).

Comenzamos agregando las predicciones con augment con el parámetro type="response". La función augment hereda el argumento type.predict de la función predict.

  • Si type.predict = 'link' la predicción es en términos de la función link. En nuestro caso son el logaritmo de las odds, es decir, los valores que toma la expresión logit.

  • Si type.predict = 'response' la predicción son las probabilidades de que la observación pertenezca a la clase positiva. En nuestro caso, devuelve la probabilidad de la que persona defaultee.

# Añadir las predicciones
models <- models %>% 
  mutate(pred= map(mod,augment, type.predict = "response"))
#Observaciones con probabilidad más baja
models$pred[1]$bal %>% arrange(.fitted) %>% head(10)
#Observaciones con probabilidad más alta
models$pred[1]$bal %>% arrange(desc(.fitted)) %>% head(10)

Guardamos las predicciones para los modelos mencionados.

# Modelo completo
prediction_full <- models %>% 
  filter(models=="full") %>% 
  unnest(pred, .drop=TRUE)
The `.drop` argument of `unnest()` is deprecated as of tidyr 1.0.0.
All list-columns are now preserved.
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
#Modelo malo
prediction_bad <- models %>% 
  filter(models=="stud_inc") %>% 
  unnest(pred, .drop=TRUE)

Violin plots

# graficamos el modelo completo
violin_full = ggplot(prediction_full, aes(x=default, y=.fitted, group=default, fill=factor(default))) + 
  geom_violin() +
  theme_bw() +
  guides(fill=FALSE) +
  labs(title='Violin plot', subtitle='Modelo completo', y='Predicted probability')
# graficamos el modelo malo
violin_bad = ggplot(prediction_bad, aes(x=default, y=.fitted, group=default, fill=factor(default))) + 
  geom_violin() + 
  theme_bw() +
  guides(fill=FALSE) +
  labs(title='Violin plot', subtitle='Modelo malo', y='Predicted probability')
# mostramos ambos
plot_grid(violin_bad, violin_full)

En los gráficos de violin observamos:

  • En el eje de abscisas la clase verdadera: Default o No Default (1 o 0).

  • En el eje de ordenadas la probabilidad predicha por nuestro modelo.

  • El gráfico nos muestra la distribución de la cantidad de observaciones por su clase real y la probabilidad que le asigna nuestro modelo.

¿Cuál parece ser un punto de corte adecuado para cada modelo?

Gráfico de Hosmer-Lemeshow

Se genera una función para realizar un gráfico de Hosmer-Lemeshow para un dataset. Para ello se fijan los siguientes parámetros:

  • dataset: conjunto de datos

  • predicted_column: columna con la probabilidad predicha

  • class_column: columna con la clase a predecir

  • possitive_value: valor de la clase a predecir

  • bins: cantidad de grupos del gráfico

  • color: color de los puntos

  • nudge_x: desplazamiento de la etiqueta en el eje x

  • nudge_y: desplazamiento de la etiqueta en el eje y

Hosmer_Lemeshow_plot <- function(dataset, predicted_column, class_column, bins, positive_value, color='forestgreen', nudge_x=0, nudge_y=0.05){
  # Asignar los grupos a las observaciones de acuerdo a la probabilidad predicha
  dataset['group'] <- bin(dataset[predicted_column], nbins = bins, method = 'l', labels=c(1:bins))
  # Contar la cantidad de casos positivos por grupo
  positive_class <- dataset %>% filter(!!sym(class_column)==positive_value) %>% group_by(group) %>% count()
  # Obtener la media de las predicciones por grupo
  HL_df <- dataset %>% group_by(group) %>% summarise(pred=mean(!!sym(predicted_column)), count=n()) %>%
            inner_join(.,positive_class) %>%
            mutate(freq=n/count)
  # Gráfico 
  HM_plot <- ggplot(HL_df, aes(x=pred, y=freq)) + 
    geom_point(aes(size=n), color=color) +
    geom_text(aes(label=n),nudge_y = nudge_y)+
    geom_abline(slope = 1, intercept = 0, linetype='dashed') + 
    theme_bw() +
    labs(title='Hosmer-Lemeshow', size='Casos', x="Probabilidad Predicha", y="Frecuencia observada")
  return(HM_plot)
}

Generamos los gráficos pasandole lo parámetros.

# modelo completo
Hosmer_Lemeshow_plot(prediction_full, '.fitted', 'default', 10, 1) +
  labs(subtitle="Modelo completo")

# modelo malo
Hosmer_Lemeshow_plot(prediction_bad, '.fitted', 'default', 10, 1, color = "firebrick", nudge_y = 0.003) + scale_x_continuous(limits = c(0.02,.06)) + scale_y_continuous(limits = c(.02,.06)) + labs(subtitle="Modelo malo")

En los gráficos de Hosmer-Lemeshow observamos:

  • En el eje de abscisas la probabilidad predicha de default.

  • En el eje de ordenadas la frecuencia de clase, el cociente entre cantidad de individuos que están en default y el total de individuos.

  • La línea punteada designa la igualdad entre probabilidad predicha y frecuencia de clase.

  • Los círculos, que se construyen de la siguiente manera:
    • Se dividen a las observaciones en bins en base a la probabilidad predicha
    • Se calcula la frecuencia de clase para cada bin
    • En base a estas dos coordenadas se ubica al círculo en el gráfico
    • El número y tamaño indican la cantidad de observaciones en dicho grupo

Aquellos círculos que se ubiquen por encima de la línea punteada indican que el modelo está subestimando la probabilidad para dichos grupos. Mientras que si los círculos se ubican por debajo el modelo está sobreestimando la probabilidad para dichos grupos.

¿Para qué valores parece existir una sobreestimación de la probabilidad? ¿Para cuáles subestimación?

Curvas ROC

# Calculamos curvas ROC
roc_full <- roc(response=prediction_full$default, predictor=prediction_full$.fitted)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
roc_bad <- roc(response=prediction_bad$default, predictor=prediction_bad$.fitted)
Setting levels: control = 0, case = 1
Setting direction: controls < cases

Graficamos ambas en un mismo plot.

ggroc(list(full=roc_full, bad=roc_bad), size=1) + 
  geom_abline(slope = 1, intercept = 1, linetype='dashed') +
  theme_bw() + 
  labs(title='Curvas ROC', color='Modelo')

print(paste('AUC: Modelo completo', round(roc_full$auc,3)))
[1] "AUC: Modelo completo 0.952"
print(paste('AUC: Modelo malo', round(roc_bad$auc,3)))
[1] "AUC: Modelo malo 0.562"

¿Qué significa cada uno de los ejes?

Punto de corte

Hasta ahora hemos evaluado el modelo de manera general, pero el resultado final del modelo debe consistir en asignar a la persona una clase predicha. En nuestro caso debemos establecer un punto de corte según el cual vamos a separar a las personas en quienes defaultean y quienes no.

Probamos varios puntos de corte y graficamos el accuracy, la sensibilidad o recall, la especificidad y la precisión para cada uno de ellos.

Clases predichas / Clases Negativa Positiva
Negativa True Neg False Neg
Positiva False Pos True Pos

Recordemos que:

\(accuracy = \frac{TP+TN}{TP+FP+FN+TN}\)

\(sensitivity = recall = \frac{TP}{TP+FN}\)

\(specificity = \frac{TN}{TN+FP}\)

\(precision = \frac{TP}{TP+FP}\)


prediction_metrics <- function(cutoff, predictions=prediction_full){
  table <- predictions %>% 
    mutate(predicted_class=if_else(.fitted>cutoff, 1, 0) %>% as.factor(),
           default= factor(default))
  
  confusionMatrix(table(table$predicted_class, table$default), positive = "1") %>%
    tidy() %>%
    select(term, estimate) %>%
    filter(term %in% c('accuracy', 'sensitivity', 'specificity', 'precision')) %>%
    mutate(cutoff=cutoff)
  
}

cutoffs = seq(0.01,0.95,0.01)
logit_pred= map_dfr(cutoffs, prediction_metrics)%>% mutate(term=as.factor(term))

ggplot(logit_pred, aes(cutoff,estimate, group=term, color=term)) + geom_line(size=1) +
  theme_bw() +
  labs(title= 'Accuracy, Sensitivity, Specificity y Precision', subtitle= 'Modelo completo', color="")

¿Qué podemos observar en el gráfico?

¿Podemos definir un buen punto de corte? ¿Cuál sería?

¿Por qué la especificidad tiene ese comportamiento?

Dataset de testing

Seleccionamos el modelo completo, ya que es el que maximizaba el porcentaje de deviance explicada y en base a lo que vimos definimos un punto de corte en 0.25 (pueden probar otros)

sel_cutoff = 0.27
# Creamos el modelo
full_model <- glm(logit_formulas$full, family = 'binomial', data = default)
# Agregamos la predicciones al dataset de testeo
table= augment(x=full_model, newdata=test, type.predict='response') 
# Clasificamos utilizamos el punto de corte
table=table %>% 
  mutate(predicted_class=if_else(.fitted>sel_cutoff, 1, 0) %>% as.factor(), 
         default= factor(default))
# Creamos la matriz de confusión
confusionMatrix(table(table$predicted_class, table$default), positive = "1")
Confusion Matrix and Statistics

   
       0    1
  0 2840   51
  1   55   54
                                         
               Accuracy : 0.9647         
                 95% CI : (0.9574, 0.971)
    No Information Rate : 0.965          
    P-Value [Acc > NIR] : 0.5652         
                                         
                  Kappa : 0.4864         
                                         
 Mcnemar's Test P-Value : 0.7708         
                                         
            Sensitivity : 0.51429        
            Specificity : 0.98100        
         Pos Pred Value : 0.49541        
         Neg Pred Value : 0.98236        
             Prevalence : 0.03500        
         Detection Rate : 0.01800        
   Detection Prevalence : 0.03633        
      Balanced Accuracy : 0.74764        
                                         
       'Positive' Class : 1              
                                         

Segunda parte

Desbalanceo de la clase

Al explorar el dataset vimos que existía un fuerte desbalance de clase. Sólo el 3% de las observaciones pertenecen a personas que defaultearon. Esto puede tener un efecto en las estimaciones del modelo y su clasificación final.

Existen dos maneras sencillas con las cuales podemos trabajar con una clase desbalanceada:

  • Sobre-muestreo (oversampling) de la clase minoritaria

  • Sub-muestreo (undersampling) de la clase mayoritaria

La función glm puede tomar como argumento una columna (weigths) de ponderadores para poder hacer esto. Podemos asignar pesos mayores a 1 a la clase minoritaria (oversampling) o menores a 1 a la clase mayoritaria (undersampling). En nuestro problema vamos a realizar un sobresampleo de la clase minoritaria.

# Creamos la columna de ponderadores
default <- default %>% mutate(wt= if_else(default==1,20,1))
# Creamos los modelos con la data 'balanceada'
balanced_models <- data_frame(logit_formulas) %>% # dataframe a partir del objeto formulas
  mutate(models = names(logit_formulas), # columna con los nombres de las formulas
         expression = paste(logit_formulas), # columna con las expresiones de las formulas
         mod = map(logit_formulas, ~glm(.,family = 'binomial', data = default, weights = wt))) #Pasamos la columna wt como ponderadores

Vemos las estimaciones de los parámetros para el modelo completo. ¿Existen cambios?

Ahora veamos la evaluación de los modelos ¿Qué pasó con el porcentaje de deviance explicada? ¿Y con la nula?

Setting levels: control = 0, case = 1
Setting direction: controls < cases
Setting levels: control = 0, case = 1
Setting direction: controls < cases

Violin plots, Curvas ROC y AUCs

Realizamos los gráficos de violin, las curvas ROC y calculamos las AUC.

[1] "AUC Modelo completo: 0.952"
[1] "AUC Modelo malo: 0.562"

¿Dónde se ven los cambios más notorios respecto a nuestros modelos anteriores que no tenían en cuenta el desbalance de la clase?

Punto de corte

Volvemos a realizar las pruebas para varios puntos de corte y graficamos el accuracy, la sensibilidad, la especificidad, el recall y la precision para cada uno de ellos.

¿Qué cambios vemos respecto al gráfico anterior?

Dataset de testing

Probamos en el dataset de testing nuestro modelo balanceado. No es necesario que le creemos pesos al dataset de testeo.

Confusion Matrix and Statistics

   
       0    1
  0 2863   55
  1   32   50
                                          
               Accuracy : 0.971           
                 95% CI : (0.9644, 0.9767)
    No Information Rate : 0.965           
    P-Value [Acc > NIR] : 0.03814         
                                          
                  Kappa : 0.52            
                                          
 Mcnemar's Test P-Value : 0.01834         
                                          
            Sensitivity : 0.47619         
            Specificity : 0.98895         
         Pos Pred Value : 0.60976         
         Neg Pred Value : 0.98115         
             Prevalence : 0.03500         
         Detection Rate : 0.01667         
   Detection Prevalence : 0.02733         
      Balanced Accuracy : 0.73257         
                                          
       'Positive' Class : 1               
                                          
LS0tCnRpdGxlOiAiUmVncmVzacOzbiBMb2fDrXN0aWNhIgphdXRob3I6ICJKdWFuIEJhcnJpb2xhIHkgU29mw61hIFBlcmluaSIKZGF0ZTogIjcgZGUgTm92aWVtYnJlIGRlIDIwMjAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KZGl2Lm1haW4tY29udGFpbmVyIHsKICBtYXgtd2lkdGg6IDE2MDBweDsKICBtYXJnaW4tbGVmdDogYXV0bzsKICBtYXJnaW4tcmlnaHQ6IGF1dG87Cn0KPC9zdHlsZT4KCiMgQ2FyZ2Ftb3MgbGFzIGxpYnJlcsOtYXMgcXVlIHZhbW9zIGEgdXRpbGl6YXIKCmBgYHtyLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHltb2RlbHMpCmxpYnJhcnkobW9kZWxyKQpsaWJyYXJ5KElTTFIpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KHBST0MpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShPbmVSKQpsaWJyYXJ5KHJsYW5nKQpsaWJyYXJ5KGNhcmV0KQpzZXQuc2VlZCgxOTkyKQpgYGAKCkxhICoqcmVncmVzacOzbiBsb2fDrXN0aWNhKiogZXMgw7p0aWwgcGFyYSBwcm9ibGVtYXMgZGUgcHJlZGljY2nDs24gZGUgY2xhc2VzLiBFbiBlc3RlIGNhc28gdmFtb3MgYSB0cmF0YXIgZGUgcmVzb2x2ZXIgZWwgcHJvYmxlbWEgZGUgcHJlZGVjaXIgc2kgdW5hIHBlcnNvbmEgdmEgZGVmYXVsdGVhciBzdSBkZXVkYSBkZSB0YXJqZXRhIGRlIGNyw6lkaXRvIGVuIGJhc2UgYSBjaWVydG9zIHByZWRpY3RvcmVzLgoKIyMgTGV2YW50YW1vcyBlbCBjb25qdW50byBkZSBkYXRvcwoKRXN0ZSBjb25qdW50byBkZSBkYXRvcyBwcm92aWVuZSBkZSBsYSBsaWJyZXLDrWEgW0lTTFJdKGh0dHA6Ly93d3ctYmNmLnVzYy5lZHUvJTdFZ2FyZXRoL0lTTC8pICAoSW50cm9kdWN0aW9uIHRvIFN0YXRpc3RpY2FsIExlYXJuaW5nIFVzaW5nIFIpIGRlIEphbWVzLCBXaXR0ZW4sIEhhc3RpZSB5IFRpYnNoaXJhbmkuCgpgYGB7cn0KIyBjYXJnYW1vcyBsb3MgZGF0b3MgeSBvYnNlcnZhbW9zIHN1IGVzdHJ1Y3R1cmEKZGVmYXVsdCA8LSBEZWZhdWx0CmdsaW1wc2UoZGVmYXVsdCkKYGBgCgpUaWVuZSA0IHZhcmlhYmxlczogCgoqICoqZGVmYXVsdCoqOiBMYSBjbGFzZSBxdWUgcXVlcmVtb3MgcHJlZGVjaXIuCgoqICoqc3R1ZGVudCoqOiBCaW5hcmlhIHF1ZSBpbmRpY2Egc2kgbGEgcGVyc29uYSBlcyBlc3R1ZGlhbnRlLgoKKiAqKmJhbGFuY2UqKjogQmFsYW5jZSBwcm9tZWRpbyBxdWUgbGUgcXVlZGEgYSBsYSBwZXJzb25hIGx1ZWdvIGRlIHN1cyBwYWdvcyBtZW5zdWFsZXMuCgoqICoqaW5jb21lKio6IEluZ3Jlc28gZGUgbGEgcGVyc29uYS4KCiMjIEV4cGxvcmF0b3JpYXMKCkFuYWxpY2Vtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBsYSBjbGFzZS4KCmBgYHtyfQpkZWZhdWx0ICU+JSAKICBncm91cF9ieShkZWZhdWx0KSAlPiUgCiAgc3VtbWFyaXNlKG51bWVyb19jYXNvcz1uKCkpCmBgYAoKVmVtb3MgcXVlIGVzdGFtb3MgdHJhYmFqYW5kbyBjb24gdW4gcHJvYmxlbWEgZGUgY2xhc2lmaWNhY2nDs24gY29uIHVuIGNsYXJvIGRlc2JhbGFuY2UgZGUgY2xhc2UuCgpSZWFsaXphbW9zIHVuIGdyw6FmaWNvIGV4cGxvcmF0b3JpbyBjb21wbGV0byBwYXJhIHZlciBlbCBjb21wb3J0YW1pZW50byB5IGxhcyByZWxhY2lvbmVzIGVudHJlIGxhcyB2YXJpYWJsZXMuIEVsIGNvbG9yIHJvam8gZGVzaWduYSBhIHF1aWVuZXMgbm8gZGVmYXVsdGVhbiB5IGVsIGF6dWwgYSBsb3MgcXVlIHPDrS4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQojIGdyYWZpY2Ftb3MgY29uIGdncGFpcnMgY29sb3JlYW5kbyBwb3IgdmFyaWFibGUgYSBwcmVkZWNpcgpnIDwtIGdncGFpcnMoZGVmYXVsdCwgMTo0LCAgdGl0bGUgPSAiQ29ycmVsb2dyYW1hIGRlIHZhcmlhYmxlcyIsCiAgICAgICAgbWFwcGluZyA9IGFlcyhjb2xvdXI9IGRlZmF1bHQpKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICsgCiAgdGhlbWVfYncoKQojIGhhY2Vtb3MgdW4gbG9vcCBwYXJhIGNhbWJpYXIgbG9zIGNvbG9yZXMgZGVsIGdyw6FmaWNvCmZvcihpIGluIDE6ZyRucm93KSB7CiAgZm9yKGogaW4gMTpnJG5jb2wpewogICAgZ1tpLGpdIDwtIGdbaSxqXSArIAogICAgICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKyAgCiAgICAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikKICAgICAgICB9Cn0KZwpgYGAKCsK/UXXDqSBwdWVkZW4gZGVjaXIgZGUgbGEgcmVsYWNpw7NuIGVudHJlIGJhbGFuY2UgeSBkZWZhdWx0PwoKwr9ZIGVudHJlIGluY29tZSB5IGJhbGFuY2U/CgrCv0N1w6FsZXMgcGFyZWNlbiBzZXIgYnVlbmFzIHZhcmlhYmxlcyBwYXJhIGRpc2NyaW1pbmFyIGVudHJlIHF1aWVuZXMgZGVmYXVsdGVhbiB5IHF1aWVuZXMgbm8/CgoKIyMjIExpbXBpZXphCgpQYXJhIG1vZGVsaXphciB2YSBhIHNlciBuZWNlc2FyaW8gdGVuZXIgbGEgdmFyaWFibGUgZGVmYXVsdCBjb21vIG51bcOpcmljYS4gCkRlZmluaW1vcyBsYSB2YXJpYWJsZSBjb21vIHswLDF9IHBhcmEgbG9zIHZhbG9yZXMgeyJObyIsIlllcyJ9CgpgYGB7cn0KZGVmYXVsdCA8LSBkZWZhdWx0ICU+JSAKICBtdXRhdGUoZGVmYXVsdCA9IGNhc2Vfd2hlbihkZWZhdWx0ID09ICJObyIgfiAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZmF1bHQgPT0gIlllcyIgfiAxKSkKZGVmYXVsdApgYGAKCiMjIFBsYW50ZW8gZGVsIFByb2JsZW1hCgpRdWVyZW1vcyBlc3RpbWFyICRQKERlZmF1bHQ9WWVzfFgpPVAoWCkkIHBhcmEgY2FkYSBpbmRpdmlkdW8geSBhIHBhcnRpciBkZSBlbGxvIHBvZGVyIGRlZmluaXIgdW4gcHVudG8gZGUgY29ydGUgcGFyYSBwcmVkZWNpciBxdWnDqW5lcyBzb24gbG9zIHF1ZSB2YW4gYSBlbnRyYXIgZW4gZGVmYXVsdC4KCiMjIyBSZWdyZXNpw7NuIGxpbmVhbAoKRW4gZXN0ZSBjYXNvIGVzdGFtb3MgbW9kZWxhbmRvIGxhIHByb2JhYmlsaWRhZCBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOiAKCiRQKFgpPSBcYmV0YV8wICsgXHN1bVxsaW1pdHNfe2o9MX1ecCBcYmV0YV9qIFgkCgpWZWFtb3MgcXVlIHRhbiBidWVubyBlcyBlbCBtb2RlbG8gbGluZWFsIHBhcmEgZXN0bywgdXNhbmRvIGJhbGFuY2UgY29tbyBwcmVkaWN0b3IuCgpgYGB7cn0KdGVzdF9tY28gPC0gZGVmYXVsdCAlPiUgCiAgICAgICAgICAgICAgbG0oZm9ybXVsYSA9IGRlZmF1bHQgfiBiYWxhbmNlLCBkYXRhID0gLikgCnRkeSA9IHRlc3RfbWNvICU+JSB0aWR5KCkgCnRkeQp0ZXN0X21jbyAlPiUgZ2xhbmNlKCkKYGBgCgpBbWJvcyBlc3RpbWFkb3JlcyBzb24gc2lnbmlmaWNhdGl2b3MgeSBlbCB0ZXN0IGRlIHNpZ25pZmljYXRpdmlkYWQgZ2xvYmFsIGRlbCBtb2RlbG8gdGFtYmnDqW4gZXMgc2lnbmlmaWNhdGl2by4KClZlYW1vcyB1biBncsOhZmljbyBkZSBudWVzdHJvIG1vZGVsby4KCmBgYHtyLCBlY2hvPUZBTFNFfQpnZ3Bsb3QoZGVmYXVsdCwgYWVzKGJhbGFuY2UsIGRlZmF1bHQpKSArIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yPWZhY3RvcihkZWZhdWx0KSkpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKyAKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSB0ZHkkZXN0aW1hdGVbMV0sIHNsb3BlID0gdGR5JGVzdGltYXRlWzJdLCBjb2xvcj0nZm9yZXN0Z3JlZW4nLCBzaXplPTIpICsgCiAgbGFicyh0aXRsZT0iTW9kZWxvIExpbmVhbCBTaW1wbGUiLCBjb2xvcj0nQ2xhc2UnKSArCiAgbGltcyh5PWMoLTEsMikpKwogIHRoZW1lX2J3KCkKYGBgCgpQYXJlY2UgdGVuZXIgYmFzdGFudGVzIHByb2JsZW1hcyBwYXJhIGVzdGltYXIgbGEgcHJvYmFiaWxpZGFkIGRlIGRlZmF1bHQgZGUgbG9zIGluZGl2aWR1b3MuIFBvciBlamVtcGxvLCB2ZW1vcyBxdWUgaGF5IHZhcmlvcyBpbmRpdmlkdW9zIGEgbG9zIGN1YWxlcyBsZXMgYXNpZ25hIHVuYSBwcm9iYWJpbGlkYWQgbmVnYXRpdmEuCgojIyMgUmVncmVzacOzbiBMb2fDrXN0aWNhCgpQYXJhIGV2aXRhciBlc3RvcyBwcm9ibGVtYXMsIHVzYW1vcyBsYSAqKmZ1bmNpw7NuIGxvZ8Otc3RpY2EqKi4KCiRQKFk9MXxYKT0gXGZyYWN7ZV57XGJldGFfMCArIFxzdW1cbGltaXRzX3tqPTF9XnAgXGJldGFfaiBYfX17MStlXntcYmV0YV8wICsgXHN1bVxsaW1pdHNfe2o9MX1ecCBcYmV0YV9qIFh9fSQKCkVsIGxhZG8gZGVyZWNobyBzZSBsbGFtYSAqKmV4cGl0KioKCkVzdGEgZnVuY2nDs24gYWNvdGEgZWwgcmVzdWx0YWRvIGVudHJlIDAgeSAxLCBsbyBjdWFsIGVzIG11Y2hvIG3DoXMgYWRlY3VhZG8gcGFyYSBtb2RlbGFyIHVuYSBwcm9iYWJpbGlkYWQuCgpMdWVnbyBkZSBoYWNlciBhbGd1bmFzIG9wZXJhY2lvbmVzLCBwb2RlbW9zIGxsZWdhciBhIGxhIGV4cHJlc2nDs246CgokXGxvZyB7XGZyYWN7UCh4KX17MS1QKHgpfX09IFxiZXRhXzAgKyBcc3VtXGxpbWl0c197aj0xfV5wIFxiZXRhX2ogWCQKCkVsIGxhZG8gaXpxdWllcmRvIGVzIGVsIGxvZ2FyaXRtbyBkZSBsb3MgKipvZGRzKiogeSBzZSBsbGFtYSAqKmxvZ2l0KiouCgojIyMgUGFydGljacOzbiBkZWwgZGF0YXNldCBlbiB0cmFpbiB5IHRlc3QKClJlYWxpemFtb3MgdW5hIHBhcnRpY2nDs24gdW5hIHBhcnRpY2nDs24gZW50cmUgZGF0YXNldCBkZSBlbnRyZW5hbWllbnRvICg3MCUpIHkgdGVzdGVvICgzMCUpIHVzYW5kbyBsYSBmdW5jacOzbiBgaW5pdGlhbF9zcGxpdGAgZGVsIHBhcXVldGUgW3JzYW1wbGVdKGh0dHBzOi8vcnNhbXBsZS50aWR5bW9kZWxzLm9yZy8pIGRlIHRpZHltb2RlbHMuCgpgYGB7cn0KIyBmaWphbW9zIHNlbWlsbGEKc2V0LnNlZWQoNDQpCiMgUGFydGljacOzbiBUcmFpbiB5IFRlc3QsIGluZGljYW5kbyBwcm9wb3JjacOzbgp0cmFpbl90ZXN0IDwtIGluaXRpYWxfc3BsaXQoZGVmYXVsdCwgcHJvcCA9IDAuNykKIyBhcm1hbW9zIGRhdGFmcmFtZSBkZSB0ZXN0ZW8geSBlbnRyZW5hbWllbnRvCmRlZmF1bHQgPC0gdHJhaW5pbmcodHJhaW5fdGVzdCkKdGVzdCA8LSB0ZXN0aW5nKHRyYWluX3Rlc3QpCiMgdmVtb3MgZWwgY29udGVuaWRvCmRlZmF1bHQgJT4lCiAgZGltX2Rlc2MoKSAjIDcwMDAgZmlsYXMKdGVzdCAlPiUKICBkaW1fZGVzYygpICMgMzAwMCBmaWxhcwpgYGAKCiMjIENyZWFjacOzbiBkZSBmw7NybXVsYXMKClBhcmEgYXBsaWNhciBsYSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgcHJpbWVybyB1c2Ftb3MgbGEgZnVuY2nDs24gYGZvcm11bGFzYCBkZWwgcGFxdWV0ZSAqKm1vZGVscioqIHBhcmEgY3JlYXIgdW4gb2JqZXRvIHF1ZSBjb250aWVuZSB0b2RhcyBsYXMgZsOzcm11bGFzIHF1ZSB2YW1vcyBhIHV0aWxpemFyLiBFbiBgLnJlc3BvbnNlYCBlc3BlY2lmaWNhbW9zIGxhIHZhcmlhYmxlIHJlc3B1ZXN0YSBkZSBudWVzdHJhcyBmw7NybXVsYXMgeSBsdWVnbyBub21icmFtb3MgbGFzIGbDs3JtdWxhcyBxdWUgcXVlcmFtb3MgYXJtYXIuCgpBc8OtLCBhcm1hcmVtb3MgZGlzdGludG9zIG1vZGVsb3MgY29tYmluYW5kbyBkaXN0aW50YXMgdmFyaWFibGVzLiAKCmBgYHtyfQpsb2dpdF9mb3JtdWxhcyA8LSBmb3JtdWxhcygucmVzcG9uc2UgPSB+IGRlZmF1bHQsICMgw7puaWNvIGxhZG8gZGVyZWNobyBkZSBsYXMgZm9ybXVsYXMuCiAgICAgICAgICAgICAgICAgICAgICAgICBiYWwgPSB+IGJhbGFuY2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgc3R1ZCA9IH4gc3R1ZGVudCwgIAogICAgICAgICAgICAgICAgICAgICAgICAgaW5jID0gfiBpbmNvbWUsICAKICAgICAgICAgICAgICAgICAgICAgICAgIGJhbF9zdHVkID0gfiBiYWxhbmNlICsgc3R1ZGVudCwgCiAgICAgICAgICAgICAgICAgICAgICAgICBiYWxfaW5jID0gfiBiYWxhbmNlICsgaW5jb21lLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHN0dWRfaW5jID0gfiBzdHVkZW50ICsgaW5jb21lLCAgCiAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsID0gfiBiYWxhbmNlICsgaW5jb21lICsgc3R1ZGVudCAgCiAgICAgICAgICAgICAgICAgICAgICAgICApCmxvZ2l0X2Zvcm11bGFzICMgb2JzZXJ2YW1vcyBlbCBvYmpldG8gZm9ybXVsYXMKYGBgCgojIyBDcmVhY2nDs24gZGUgbW9kZWxvcwoKUHJvY2VkZW1vcyBhIGNyZWFyIGxvcyBtb2RlbG9zIGEgcGFydGlyIGRlIGVzdGFzIGbDs3JtdWxhcy4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQptb2RlbHMgPC0gZGF0YV9mcmFtZShsb2dpdF9mb3JtdWxhcykgJT4lICMgZGF0YWZyYW1lIGEgcGFydGlyIGRlbCBvYmpldG8gZm9ybXVsYXMKICBtdXRhdGUobW9kZWxzID0gbmFtZXMobG9naXRfZm9ybXVsYXMpLCAjIGNvbHVtbmEgY29uIGxvcyBub21icmVzIGRlIGxhcyBmb3JtdWxhcwogICAgICAgICBleHByZXNzaW9uID0gcGFzdGUobG9naXRfZm9ybXVsYXMpLCAjIGNvbHVtbmEgY29uIGxhcyBleHByZXNpb25lcyBkZSBsYXMgZm9ybXVsYXMKICAgICAgICAgbW9kID0gbWFwKGxvZ2l0X2Zvcm11bGFzLCB+Z2xtKC4sIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSBkZWZhdWx0KSkpICMgUXVlIGVzdGFtb3MgaGFjaWVuZG8gYWPDoT8gUXVlIHZhbW9zIGEgZW5jb250cmFyIGVuIGxhIGNvbHVtbmE/Cm1vZGVscwpgYGAKCkxhIGZ1bmPDrcOzbiBgZ2xtKClgIG5vcyBwZXJtaXRlIGNyZWFyIHVuIG1vZGVsbyBsaW5lYWwgZ2VuZXJhbGl6YWRvIChHZW5lcmFsaXplZCBMaW5lYXIgTW9kZWwpLiBBbCBpZ3VhbCBxdWUgbGEgZnVuY2nDs24gYGxtKClgIHRvbWEgY29tbyBhcmd1bWVudG9zIHVuYSAqKmZvcm11bGEqKiB5IGxvcyAqKmRhdG9zKiogcGVybyB0YW1iacOpbiBzZSBkZWJlIGVzcGVjaWZpY2FyIGVsIGFyZ3VtZW50byAqKmZhbWlseSoqOiBpbmRpY2Ftb3MgbGEgZGlzdHJpYnVjacOzbiBkZWwgZXJyb3IgeSBsYSBmdW5jacOzbiBsaW5rIHF1ZSB2YW1vcyBhIHV0aWxpemFyIGVuIGVsIG1vZGVsby4gCgpBbGd1bmFzIGZhbWlsaWFzIHNvbjoKCiogQmlub21pYWw6IGxpbms9bG9naXQKCiogUG9pc3NvbjogbGluaz1sb2cKCiogR2F1c3NpYW5hOiBsaW5rPWlkZW50aWRhZAoKQ29tbyBlc3RhbW9zIHRyYWJhamFuZG8gY29uIHVuIGZlbsOzbWVubyBxdWUgc3Vwb25lbW9zIHRpZW5lIHVuYSBkaXN0cmlidWNpw7NuIGJpbm9taWFsLCBhc8OtIGxvIGVzcGVjaWZpY2Ftb3MgZW4gZWwgcGFyw6FtZXRybyAqKmZhbWlseSoqLgoKIyMjIE1vZGVsb3Mgc2ltcGxlcwoKUHJvYmFtb3MgbG9zIHByaW1lcm9zIHRyZXMgbW9kZWxvcywgYXF1ZWxsb3MgcXVlIHRpZW5lbiB1biDDum5pY28gcHJlZGljdG9yLiBVc2Ftb3MgbGEgZnVuY2nDs24gX3RpZHlfIHBhcmEgb2J0ZW5lciBsb3MgcGFyw6FtZXRyb3MgZXN0aW1hZG9zIHBhcmEgZXN0b3MgdHJlcyBtb2RlbG9zLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9Cm1vZGVscyAlPiUgCiAgZmlsdGVyKG1vZGVscyAlaW4lIGMoJ2JhbCcsJ3N0dWQnLCdpbmMnKSkgJT4lCiAgbXV0YXRlKHRpZHkgPSBtYXAobW9kLCB0aWR5KSkgICMgUXXDqSByZWFsaXphbW9zIGVuIGVzdGUgcGFzbz8gUXVlIHZhIGEgdGVuZXIgZXN0YSBjb2x1bW5hPwpgYGAKClBhcmEgYWNjZWRlciBhIGxvcyBlbGVtZW50b3MgZGUgbGEgbnVldmEgY29sdW1uYSBfdGlkeV8gZGViZW1vcyBkZXNhbmlkYXJsYS4gCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KbW9kZWxzICU+JSAKICBmaWx0ZXIobW9kZWxzICVpbiUgYygnYmFsJywnc3R1ZCcsJ2luYycpKSAlPiUKICBtdXRhdGUodGlkeSA9IG1hcChtb2QsIHRpZHkpKSAlPiUgICMgUXXDqSByZWFsaXphbW9zIGVuIGVzdGUgcGFzbz8gUXVlIHZhIGEgdGVuZXIgZXN0YSBjb2x1bW5hPwogIHVubmVzdCh0aWR5KSAlPiUgCiAgbXV0YXRlKGVzdGltYXRlPXJvdW5kKGVzdGltYXRlLDUpLCAjIHJlZG9uZGVhbW9zIHZhbG9yZXMgcGFyYSBmYWNpbGl0YXIgbGVjdHVyYQogICAgICAgICBwLnZhbHVlPXJvdW5kKHAudmFsdWUsNCkpCmBgYAoKT2JzZXJ2YW1vcyBxdWUgdG9kb3MgbG9zIG1vZGVsb3MgdGllbmVuIGNvZWZpY2llbnRlcyBzaWduaWZpY2F0aXZvcyBleGNlcHRvIGVsIGNvZWZpY2llbnRlIGFzb2NpYWRvIGFsICoqSW5ncmVzbyoqIChpbmNvbWUpLCBlbCBjdWFsIHRpZW5lIHVuIHAtdmFsb3IgZGUgMC4xNy4KCiMjIyMgSW50ZXJwcmV0YWNpw7NuIGRlIGxvcyBjb2VmaWNpZW50ZXMKClJlY29yZGFuZG8gbGEgZWN1YWNpw7NuIHBhcmEgbW9kZWxhciBsYSBwcm9iYWJpbGlkYWQ6CgokUChZPTF8WCk9IFxmcmFje2Vee1xiZXRhXzAgKyBcc3VtXGxpbWl0c197aj0xfV5wIFxiZXRhX2ogWH19ezErZV57XGJldGFfMCArIFxzdW1cbGltaXRzX3tqPTF9XnAgXGJldGFfaiBYfX0kCgpTZSBvYnNlcnZhIHF1ZSBhaG9yYSBsYXMgdmFyaWFibGVzIHlhIG5vIHRpZW5lbiB1bmEgcmVsYWNpw7NuIGxpbmVhbCBjb24gbGEgcHJvYmFiaWxpZGFkLiBFbiBlc3RlIG1vZGVsbyB1biBjb2VmaWNpZW50ZSBwb3NpdGl2byBpbmRpY2EgcXVlIGZyZW50ZSBhIGF1bWVudG9zIGRlIGRpY2hhIHZhcmlhYmxlIGxhIHByb2JhYmlsaWRhZCBhdW1lbnRhLCBtaWVudHJhcyBxdWUgdW4gY29lZmljaWVudGUgbmVnYXRpdm8gbm9zIGluZGljYSBsbyBjb250cmFyaW8uIFBhcmEgbnVlc3RybyBjYXNvOgoKKiBFbCBjb2VmaWNpZW50ZSBkZSAwLjAwNTYgZGUgbGEgdmFyaWFibGUgKipiYWxhbmNlKiogaW5kaWNhIHF1ZSBjdWFuZG8gZWwgYmFsYW5jZSBhdW1lbnRhIGxhICoqcHJvYmFiaWxpZGFkIGRlIGRlZmF1bHQgYXVtZW50YSoqLgoKKiBFbCBjb2VmaWNpZW50ZSBkZSAwLjQwODg2IGRlIGxhIHZhcmlhYmxlICoqc3R1ZGVudFllcyoqIGluZGljYSBxdWUgY3VhbmRvIHVuYSBwZXJzb25hIGVzIGVzdHVkaWFudGUgc3UgKipwcm9iYWJpbGlkYWQgZGUgZGVmYXVsdCBhdW1lbnRhKiogcmVzcGVjdG8gYSB1bmEgcGVyc29uYSBxdWUgbm8gbG8gZXMuCgoqIEVsIGNvZWZpY2llbnRlIGRlIC0wLjAwMDAxIGRlIGxhIHZhcmlhYmxlICoqaW5jb21lKiogaW5kaWNhIHF1ZSBjdWFuZG8gZWwgaW5ncmVzbyBhdW1lbnRhICoqbGEgcHJvYmFiaWxpZGFkIGRlIGRlZmF1bHQgZGlzbWludXllKiouCgpSZWNvbWVuZGFtb3MgbGVlciBlbCBbY2Fww610dWxvIGRlIHJlZ3Jlc2nDs24gbG9nw61zdGljYV0oaHR0cHM6Ly9jaHJpc3RvcGhtLmdpdGh1Yi5pby9pbnRlcnByZXRhYmxlLW1sLWJvb2svbG9naXN0aWMuaHRtbCkgZGUgKkludGVycHJldGFibGUgTWFjaGluZSBMZWFybmluZzogQSBHdWlkZSBmb3IgTWFraW5nIEJsYWNrIEJveCBNb2RlbHMgRXhwbGFpbmFibGUqIGRlIE1vbG5hciBDcmlzdG9waCBwYXJhIHVuYSBkaXNjdXNpw7NuIG3DoXMgcHJvZnVuZGEgZGUgbGEgaW50ZXJwcmV0YWNpw7NuIGRlIHVuIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EuCgojIyMgTW9kZWxvIGNvbXBsZXRvCgpBaG9yYSBwcm9iYW1vcyBjb24gdW4gbW9kZWxvIHF1ZSB1dGlsaXphIGxhcyB0cmVzIHByZWRpY3RvcmFzLgoKYGBge3IsICB3YXJuaW5nPUZBTFNFfQptb2RlbHMgJT4lIAogIGZpbHRlcihtb2RlbHMgPT0gImZ1bGwiKSAlPiUKICBtdXRhdGUodGlkeSA9IG1hcChtb2QsdGlkeSkpICU+JQogIHVubmVzdCh0aWR5KSAlPiUgCiAgbXV0YXRlKGVzdGltYXRlPXJvdW5kKGVzdGltYXRlLDUpLAogICAgICAgICBwLnZhbHVlPXJvdW5kKHAudmFsdWUsNCkpCmBgYAoKRW4gZWwgbW9kZWxvIGNvbiBsYXMgdHJlcyB2YXJpYWJsZXMgbG9zIGNvZWZpY2llbnRlcyBkZSAqKnN0dWRlbnRZZXMqKiBlICoqaW5jb21lKiogaGFuIGNhbWJpYWRvIGRlIHNpZ25vLCBwb3IgbG8gY3VhbCBzdSBlZmVjdG8gc29icmUgbGEgcHJvYmFiaWxpZGFkIGRlIGRlZmF1bHQgZXMgaW52ZXJzbyBhbCBxdWUgb2JzZXJ2YW1vcyBlbiBsb3MgbW9kZWxvcyBzaW1wbGVzLiBUYW1iacOpbiBoYXkgcXVlIGFncmVnYXIgYWhvcmEgcXVlIGVsIGVmZWN0byBkZSBsYSB2YXJpYWJsZSBzZSBvYnNlcnZhIGFsIGVzdGFyIGNvbnRyb2xhbmRvIHBvciBsYXMgZG9zIGNvdmFyaWFibGVzIHJlc3RhbnRlcy4KCkFzaW1pc21vLCBzZSBvYnNlcnZhIHF1ZSBlbCBjb2VmaWNpZW50ZSBkZSBpbmNvbWUgbm8gcmVzdWx0YSBzaWduaWZpY2F0aXZvIHRhbXBvY28gZW4gZXN0ZSBtb2RlbG8gKHAtdmFsb3I+MC4wNSkuIAoKIyMjIEV2YWx1YWNpw7NuIGRlIHRvZG9zIGxvcyBtb2RlbG9zCgpDb24gYG1hcCgpYCBhZ3JlZ2Ftb3MgbGEgZnVuY2nDs24gYGdsYW5jZWAgcGFyYSB0cmFlciBpbmZvcm1hY2nDs24gcmVsZXZhbnRlIHBhcmEgbGEgZXZhbHVhY2nDs24gZGVsIG1vZGVsby4gQ29uIGB1bm5lc3QoKWAgYWNjZWRlbW9zIGEgZGljaGEgaW5mb3JtYWNpw7NuLiBQb3Igw7psdGltbywgYWdyZWdhbW9zIHVuYSBjb2x1bW5hIGNvbiBlbCBwb3JjZW50YWplIGRlIGRldmlhbmNlIGV4cGxpY2FkbyBwb3IgY2FkYSBtb2RlbG8geSBvcmRlbmFtb3MgZWwgZGF0YXNldCBzZWfDum4gc3UgdmFsb3IgZGUgZGV2aWFuY2UuCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KIyBDYWxjdWxhciBsYXMgbWVkaWRhcyBkZSBldmFsdWFjacOzbiBwYXJhIGNhZGEgbW9kZWxvCm1vZGVscyA8LSBtb2RlbHMgJT4lIAogIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kLGdsYW5jZSkpCiMgT2J0ZW5lciBsYXMgbWVkaWRhcyBkZSBldmFsdWFjaW9uIGRlIGludGVyZXMKbW9kZWxzICU+JSAKICB1bm5lc3QoZ2xhbmNlKSAlPiUKICAjIENhbGN1bGFtb3MgbGEgZGV2aWFuY2UgZXhwbGljYWRhCiAgbXV0YXRlKHBlcmNfZXhwbGFpbmVkX2RldiA9IDEtZGV2aWFuY2UvbnVsbC5kZXZpYW5jZSkgJT4lIAogIHNlbGVjdCgtYyhtb2RlbHMsIGRmLm51bGwsIEFJQywgQklDKSkgJT4lIAogIGFycmFuZ2UoZGV2aWFuY2UpCmBgYAoKRWwgbW9kZWxvIHF1ZSB1dGlsaXphIGxhcyAzIHZhcmlhYmxlcyBlcyBlbCBxdWUgbWluaW1pemEgZWwgZGV2aWFuY2UsIGF1bnF1ZSBlbCBtb2RlbG8gcXVlIGV4Y2x1eWUgYSBsYSB2YXJpYWJsZSBpbmNvbWUgKHBvY28gc2lnbmlmaWNhdGl2YSkgZXN0w6EgbXV5IGNlcmNhLiBMb3MgMyDDumx0aW1vcyBtb2RlbG9zIHJlZHVjZW4gbXV5IHBvY28gZWwgZGV2aWFuY2UgcmVzcGVjdG8gYSBsYSBkZXZpYW5jZSBudWxhLgoKIyMjIEdyw6FmaWNvcyBkZSBFdmFsdWFjacOzbgoKUmVhbGl6YW1vcyBsb3MgZ3LDoWZpY29zIHBhcmEgZWwgbW9kZWxvIGNvbXBsZXRvIHkgdW5vIGRlIGxvcyBtb2RlbG9zIGNvbiBtYXlvciBkZXZpYW5jZSAoc3R1ZGVudCtpbmNvbWUpLgoKQ29tZW56YW1vcyBhZ3JlZ2FuZG8gbGFzIHByZWRpY2Npb25lcyBjb24gYGF1Z21lbnRgIGNvbiBlbCBwYXLDoW1ldHJvIGB0eXBlPSJyZXNwb25zZSJgLiBMYSBmdW5jacOzbiBhdWdtZW50IGhlcmVkYSBlbCBhcmd1bWVudG8gdHlwZS5wcmVkaWN0IGRlIGxhIGZ1bmNpw7NuIHByZWRpY3QuCgogICogU2kgYHR5cGUucHJlZGljdCA9ICdsaW5rJ2AgbGEgcHJlZGljY2nDs24gZXMgZW4gdMOpcm1pbm9zIGRlIGxhIGZ1bmNpw7NuIGxpbmsuIEVuIG51ZXN0cm8gY2FzbyBzb24gZWwgbG9nYXJpdG1vIGRlIGxhcyBvZGRzLCBlcyBkZWNpciwgbG9zIHZhbG9yZXMgcXVlIHRvbWEgbGEgZXhwcmVzacOzbiBsb2dpdC4KICAKICAqIFNpIGB0eXBlLnByZWRpY3QgPSAncmVzcG9uc2UnYCBsYSBwcmVkaWNjacOzbiBzb24gbGFzIHByb2JhYmlsaWRhZGVzIGRlIHF1ZSBsYSBvYnNlcnZhY2nDs24gcGVydGVuZXpjYSBhIGxhIGNsYXNlIHBvc2l0aXZhLiBFbiBudWVzdHJvIGNhc28sIGRldnVlbHZlIGxhIHByb2JhYmlsaWRhZCBkZSBsYSBxdWUgcGVyc29uYSBkZWZhdWx0ZWUuCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KIyBBw7FhZGlyIGxhcyBwcmVkaWNjaW9uZXMKbW9kZWxzIDwtIG1vZGVscyAlPiUgCiAgbXV0YXRlKHByZWQ9IG1hcChtb2QsYXVnbWVudCwgdHlwZS5wcmVkaWN0ID0gInJlc3BvbnNlIikpCiNPYnNlcnZhY2lvbmVzIGNvbiBwcm9iYWJpbGlkYWQgbcOhcyBiYWphCm1vZGVscyRwcmVkWzFdJGJhbCAlPiUgYXJyYW5nZSguZml0dGVkKSAlPiUgaGVhZCgxMCkKI09ic2VydmFjaW9uZXMgY29uIHByb2JhYmlsaWRhZCBtw6FzIGFsdGEKbW9kZWxzJHByZWRbMV0kYmFsICU+JSBhcnJhbmdlKGRlc2MoLmZpdHRlZCkpICU+JSBoZWFkKDEwKQpgYGAKCkd1YXJkYW1vcyBsYXMgcHJlZGljY2lvbmVzIHBhcmEgbG9zIG1vZGVsb3MgbWVuY2lvbmFkb3MuCgpgYGB7cn0KIyBNb2RlbG8gY29tcGxldG8KcHJlZGljdGlvbl9mdWxsIDwtIG1vZGVscyAlPiUgCiAgZmlsdGVyKG1vZGVscz09ImZ1bGwiKSAlPiUgCiAgdW5uZXN0KHByZWQsIC5kcm9wPVRSVUUpCiNNb2RlbG8gbWFsbwpwcmVkaWN0aW9uX2JhZCA8LSBtb2RlbHMgJT4lIAogIGZpbHRlcihtb2RlbHM9PSJzdHVkX2luYyIpICU+JSAKICB1bm5lc3QocHJlZCwgLmRyb3A9VFJVRSkKYGBgCgoKIyMjIyBWaW9saW4gcGxvdHMKCmBgYHtyfQojIGdyYWZpY2Ftb3MgZWwgbW9kZWxvIGNvbXBsZXRvCnZpb2xpbl9mdWxsID0gZ2dwbG90KHByZWRpY3Rpb25fZnVsbCwgYWVzKHg9ZGVmYXVsdCwgeT0uZml0dGVkLCBncm91cD1kZWZhdWx0LCBmaWxsPWZhY3RvcihkZWZhdWx0KSkpICsgCiAgZ2VvbV92aW9saW4oKSArCiAgdGhlbWVfYncoKSArCiAgZ3VpZGVzKGZpbGw9RkFMU0UpICsKICBsYWJzKHRpdGxlPSdWaW9saW4gcGxvdCcsIHN1YnRpdGxlPSdNb2RlbG8gY29tcGxldG8nLCB5PSdQcmVkaWN0ZWQgcHJvYmFiaWxpdHknKQojIGdyYWZpY2Ftb3MgZWwgbW9kZWxvIG1hbG8KdmlvbGluX2JhZCA9IGdncGxvdChwcmVkaWN0aW9uX2JhZCwgYWVzKHg9ZGVmYXVsdCwgeT0uZml0dGVkLCBncm91cD1kZWZhdWx0LCBmaWxsPWZhY3RvcihkZWZhdWx0KSkpICsgCiAgZ2VvbV92aW9saW4oKSArIAogIHRoZW1lX2J3KCkgKwogIGd1aWRlcyhmaWxsPUZBTFNFKSArCiAgbGFicyh0aXRsZT0nVmlvbGluIHBsb3QnLCBzdWJ0aXRsZT0nTW9kZWxvIG1hbG8nLCB5PSdQcmVkaWN0ZWQgcHJvYmFiaWxpdHknKQojIG1vc3RyYW1vcyBhbWJvcwpwbG90X2dyaWQodmlvbGluX2JhZCwgdmlvbGluX2Z1bGwpCmBgYAoKRW4gbG9zIGdyw6FmaWNvcyBkZSB2aW9saW4gb2JzZXJ2YW1vczoKCiAgKiBFbiBlbCBlamUgZGUgYWJzY2lzYXMgbGEgY2xhc2UgdmVyZGFkZXJhOiBEZWZhdWx0IG8gTm8gRGVmYXVsdCAoMSBvIDApLgogIAogICogRW4gZWwgZWplIGRlIG9yZGVuYWRhcyBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEgcG9yIG51ZXN0cm8gbW9kZWxvLgogIAogICogRWwgZ3LDoWZpY28gbm9zIG11ZXN0cmEgbGEgZGlzdHJpYnVjacOzbiBkZSBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIHBvciBzdSBjbGFzZSByZWFsIHkgbGEgcHJvYmFiaWxpZGFkIHF1ZSBsZSBhc2lnbmEgbnVlc3RybyBtb2RlbG8uCgrCv0N1w6FsIHBhcmVjZSBzZXIgdW4gcHVudG8gZGUgY29ydGUgYWRlY3VhZG8gcGFyYSBjYWRhIG1vZGVsbz8KCiMjIyMgR3LDoWZpY28gZGUgSG9zbWVyLUxlbWVzaG93CgpTZSBnZW5lcmEgdW5hIGZ1bmNpw7NuIHBhcmEgcmVhbGl6YXIgdW4gZ3LDoWZpY28gZGUgSG9zbWVyLUxlbWVzaG93IHBhcmEgdW4gZGF0YXNldC4gUGFyYSBlbGxvIHNlIGZpamFuIGxvcyBzaWd1aWVudGVzIHBhcsOhbWV0cm9zOiAKICAKKiBkYXRhc2V0OiBjb25qdW50byBkZSBkYXRvcwoKKiBwcmVkaWN0ZWRfY29sdW1uOiBjb2x1bW5hIGNvbiBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEKCiogY2xhc3NfY29sdW1uOiBjb2x1bW5hIGNvbiBsYSBjbGFzZSBhIHByZWRlY2lyCgoqIHBvc3NpdGl2ZV92YWx1ZTogdmFsb3IgZGUgbGEgY2xhc2UgYSBwcmVkZWNpcgoKKiBiaW5zOiBjYW50aWRhZCBkZSBncnVwb3MgZGVsIGdyw6FmaWNvCgoqIGNvbG9yOiBjb2xvciBkZSBsb3MgcHVudG9zCgoqIG51ZGdlX3g6IGRlc3BsYXphbWllbnRvIGRlIGxhIGV0aXF1ZXRhIGVuIGVsIGVqZSB4CgoqIG51ZGdlX3k6IGRlc3BsYXphbWllbnRvIGRlIGxhIGV0aXF1ZXRhIGVuIGVsIGVqZSB5CgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KSG9zbWVyX0xlbWVzaG93X3Bsb3QgPC0gZnVuY3Rpb24oZGF0YXNldCwgcHJlZGljdGVkX2NvbHVtbiwgY2xhc3NfY29sdW1uLCBiaW5zLCBwb3NpdGl2ZV92YWx1ZSwgY29sb3I9J2ZvcmVzdGdyZWVuJywgbnVkZ2VfeD0wLCBudWRnZV95PTAuMDUpewogICMgQXNpZ25hciBsb3MgZ3J1cG9zIGEgbGFzIG9ic2VydmFjaW9uZXMgZGUgYWN1ZXJkbyBhIGxhIHByb2JhYmlsaWRhZCBwcmVkaWNoYQogIGRhdGFzZXRbJ2dyb3VwJ10gPC0gYmluKGRhdGFzZXRbcHJlZGljdGVkX2NvbHVtbl0sIG5iaW5zID0gYmlucywgbWV0aG9kID0gJ2wnLCBsYWJlbHM9YygxOmJpbnMpKQogICMgQ29udGFyIGxhIGNhbnRpZGFkIGRlIGNhc29zIHBvc2l0aXZvcyBwb3IgZ3J1cG8KICBwb3NpdGl2ZV9jbGFzcyA8LSBkYXRhc2V0ICU+JSBmaWx0ZXIoISFzeW0oY2xhc3NfY29sdW1uKT09cG9zaXRpdmVfdmFsdWUpICU+JSBncm91cF9ieShncm91cCkgJT4lIGNvdW50KCkKICAjIE9idGVuZXIgbGEgbWVkaWEgZGUgbGFzIHByZWRpY2Npb25lcyBwb3IgZ3J1cG8KICBITF9kZiA8LSBkYXRhc2V0ICU+JSBncm91cF9ieShncm91cCkgJT4lIHN1bW1hcmlzZShwcmVkPW1lYW4oISFzeW0ocHJlZGljdGVkX2NvbHVtbikpLCBjb3VudD1uKCkpICU+JQogICAgICAgICAgICBpbm5lcl9qb2luKC4scG9zaXRpdmVfY2xhc3MpICU+JQogICAgICAgICAgICBtdXRhdGUoZnJlcT1uL2NvdW50KQogICMgR3LDoWZpY28gCiAgSE1fcGxvdCA8LSBnZ3Bsb3QoSExfZGYsIGFlcyh4PXByZWQsIHk9ZnJlcSkpICsgCiAgICBnZW9tX3BvaW50KGFlcyhzaXplPW4pLCBjb2xvcj1jb2xvcikgKwogICAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1uKSxudWRnZV95ID0gbnVkZ2VfeSkrCiAgICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGxpbmV0eXBlPSdkYXNoZWQnKSArIAogICAgdGhlbWVfYncoKSArCiAgICBsYWJzKHRpdGxlPSdIb3NtZXItTGVtZXNob3cnLCBzaXplPSdDYXNvcycsIHg9IlByb2JhYmlsaWRhZCBQcmVkaWNoYSIsIHk9IkZyZWN1ZW5jaWEgb2JzZXJ2YWRhIikKICByZXR1cm4oSE1fcGxvdCkKfQpgYGAKCkdlbmVyYW1vcyBsb3MgZ3LDoWZpY29zIHBhc2FuZG9sZSBsbyBwYXLDoW1ldHJvcy4gCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBtb2RlbG8gY29tcGxldG8KSG9zbWVyX0xlbWVzaG93X3Bsb3QocHJlZGljdGlvbl9mdWxsLCAnLmZpdHRlZCcsICdkZWZhdWx0JywgMTAsIDEpICsKICBsYWJzKHN1YnRpdGxlPSJNb2RlbG8gY29tcGxldG8iKQojIG1vZGVsbyBtYWxvCkhvc21lcl9MZW1lc2hvd19wbG90KHByZWRpY3Rpb25fYmFkLCAnLmZpdHRlZCcsICdkZWZhdWx0JywgMTAsIDEsIGNvbG9yID0gImZpcmVicmljayIsIG51ZGdlX3kgPSAwLjAwMykgKyBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLjAyLC4wNikpICsgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoLjAyLC4wNikpICsgbGFicyhzdWJ0aXRsZT0iTW9kZWxvIG1hbG8iKQoKYGBgCgpFbiBsb3MgKipncsOhZmljb3MgZGUgSG9zbWVyLUxlbWVzaG93Kiogb2JzZXJ2YW1vczoKCiAgKiBFbiBlbCBlamUgZGUgYWJzY2lzYXMgbGEgcHJvYmFiaWxpZGFkIHByZWRpY2hhIGRlIGRlZmF1bHQuCiAgCiAgKiBFbiBlbCBlamUgZGUgb3JkZW5hZGFzIGxhIGZyZWN1ZW5jaWEgZGUgY2xhc2UsIGVsIGNvY2llbnRlIGVudHJlIGNhbnRpZGFkIGRlIGluZGl2aWR1b3MgcXVlIGVzdMOhbiBlbiBkZWZhdWx0IHkgZWwgdG90YWwgZGUgaW5kaXZpZHVvcy4KICAKICAqIExhIGzDrW5lYSBwdW50ZWFkYSBkZXNpZ25hIGxhIGlndWFsZGFkIGVudHJlIHByb2JhYmlsaWRhZCBwcmVkaWNoYSB5IGZyZWN1ZW5jaWEgZGUgY2xhc2UuCiAgCiAgKiBMb3MgY8OtcmN1bG9zLCBxdWUgc2UgY29uc3RydXllbiBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOgogICAgICAqIFNlIGRpdmlkZW4gYSBsYXMgb2JzZXJ2YWNpb25lcyBlbiBiaW5zIGVuIGJhc2UgYSBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEKICAgICAgKiBTZSBjYWxjdWxhIGxhIGZyZWN1ZW5jaWEgZGUgY2xhc2UgcGFyYSBjYWRhIGJpbgogICAgICAqIEVuIGJhc2UgYSBlc3RhcyBkb3MgY29vcmRlbmFkYXMgc2UgdWJpY2EgYWwgY8OtcmN1bG8gZW4gZWwgZ3LDoWZpY28KICAgICAgKiBFbCBuw7ptZXJvIHkgdGFtYcOxbyBpbmRpY2FuIGxhIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgZW4gZGljaG8gZ3J1cG8KCkFxdWVsbG9zICoqY8OtcmN1bG9zIHF1ZSBzZSB1YmlxdWVuIHBvciBlbmNpbWEqKiBkZSBsYSBsw61uZWEgcHVudGVhZGEgaW5kaWNhbiBxdWUgZWwgKiptb2RlbG8gZXN0w6Egc3ViZXN0aW1hbmRvKiogbGEgcHJvYmFiaWxpZGFkIHBhcmEgZGljaG9zIGdydXBvcy4gTWllbnRyYXMgcXVlIHNpIGxvcyAqKmPDrXJjdWxvcyBzZSB1YmljYW4gcG9yIGRlYmFqbyoqIGVsIG1vZGVsbyBlc3TDoSAqKnNvYnJlZXN0aW1hbmRvKiogbGEgcHJvYmFiaWxpZGFkIHBhcmEgZGljaG9zIGdydXBvcy4KCsK/UGFyYSBxdcOpIHZhbG9yZXMgcGFyZWNlIGV4aXN0aXIgdW5hIHNvYnJlZXN0aW1hY2nDs24gZGUgbGEgcHJvYmFiaWxpZGFkPyDCv1BhcmEgY3XDoWxlcyBzdWJlc3RpbWFjacOzbj8KCiMjIyMgQ3VydmFzIFJPQwoKYGBge3J9CiMgQ2FsY3VsYW1vcyBjdXJ2YXMgUk9DCnJvY19mdWxsIDwtIHJvYyhyZXNwb25zZT1wcmVkaWN0aW9uX2Z1bGwkZGVmYXVsdCwgcHJlZGljdG9yPXByZWRpY3Rpb25fZnVsbCQuZml0dGVkKQpyb2NfYmFkIDwtIHJvYyhyZXNwb25zZT1wcmVkaWN0aW9uX2JhZCRkZWZhdWx0LCBwcmVkaWN0b3I9cHJlZGljdGlvbl9iYWQkLmZpdHRlZCkKCmBgYAoKR3JhZmljYW1vcyBhbWJhcyBlbiB1biBtaXNtbyBwbG90LgoKYGBge3J9Cmdncm9jKGxpc3QoZnVsbD1yb2NfZnVsbCwgYmFkPXJvY19iYWQpLCBzaXplPTEpICsgCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAxLCBsaW5ldHlwZT0nZGFzaGVkJykgKwogIHRoZW1lX2J3KCkgKyAKICBsYWJzKHRpdGxlPSdDdXJ2YXMgUk9DJywgY29sb3I9J01vZGVsbycpCnByaW50KHBhc3RlKCdBVUM6IE1vZGVsbyBjb21wbGV0bycsIHJvdW5kKHJvY19mdWxsJGF1YywzKSkpCnByaW50KHBhc3RlKCdBVUM6IE1vZGVsbyBtYWxvJywgcm91bmQocm9jX2JhZCRhdWMsMykpKQoKYGBgCgrCv1F1w6kgc2lnbmlmaWNhIGNhZGEgdW5vIGRlIGxvcyBlamVzPwoKIyMjIFB1bnRvIGRlIGNvcnRlCgpIYXN0YSBhaG9yYSBoZW1vcyBldmFsdWFkbyBlbCBtb2RlbG8gZGUgbWFuZXJhIGdlbmVyYWwsIHBlcm8gZWwgcmVzdWx0YWRvIGZpbmFsIGRlbCBtb2RlbG8gZGViZSBjb25zaXN0aXIgZW4gYXNpZ25hciBhIGxhIHBlcnNvbmEgdW5hIGNsYXNlIHByZWRpY2hhLiBFbiBudWVzdHJvIGNhc28gZGViZW1vcyBlc3RhYmxlY2VyIHVuIHB1bnRvIGRlIGNvcnRlIHNlZ8O6biBlbCBjdWFsIHZhbW9zIGEgc2VwYXJhciBhIGxhcyBwZXJzb25hcyBlbiBxdWllbmVzIGRlZmF1bHRlYW4geSBxdWllbmVzIG5vLgoKUHJvYmFtb3MgdmFyaW9zIHB1bnRvcyBkZSBjb3J0ZSB5IGdyYWZpY2Ftb3MgZWwgYWNjdXJhY3ksIGxhIHNlbnNpYmlsaWRhZCBvIHJlY2FsbCwgbGEgZXNwZWNpZmljaWRhZCB5IGxhIHByZWNpc2nDs24gcGFyYSBjYWRhIHVubyBkZSBlbGxvcy4KCnwgQ2xhc2VzIHByZWRpY2hhcyAvIENsYXNlcyB8IE5lZ2F0aXZhIHwgUG9zaXRpdmEgfAp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS18CnwgTmVnYXRpdmEgICAgICAgICAgICAgICAgIHwgVHJ1ZSBOZWcgfCBGYWxzZSBOZWcgfAp8IFBvc2l0aXZhICAgICAgICAgICAgICAgICB8IEZhbHNlIFBvcyB8IFRydWUgUG9zIHwKClJlY29yZGVtb3MgcXVlOgoKJGFjY3VyYWN5ID0gXGZyYWN7VFArVE59e1RQK0ZQK0ZOK1ROfSQKCiRzZW5zaXRpdml0eSA9IHJlY2FsbCA9IFxmcmFje1RQfXtUUCtGTn0kCgokc3BlY2lmaWNpdHkgPSBcZnJhY3tUTn17VE4rRlB9JAoKJHByZWNpc2lvbiA9IFxmcmFje1RQfXtUUCtGUH0kCgoKYGBge3J9CgpwcmVkaWN0aW9uX21ldHJpY3MgPC0gZnVuY3Rpb24oY3V0b2ZmLCBwcmVkaWN0aW9ucz1wcmVkaWN0aW9uX2Z1bGwpewogIHRhYmxlIDwtIHByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPmN1dG9mZiwgMSwgMCkgJT4lIGFzLmZhY3RvcigpLAogICAgICAgICAgIGRlZmF1bHQ9IGZhY3RvcihkZWZhdWx0KSkKICAKICBjb25mdXNpb25NYXRyaXgodGFibGUodGFibGUkcHJlZGljdGVkX2NsYXNzLCB0YWJsZSRkZWZhdWx0KSwgcG9zaXRpdmUgPSAiMSIpICU+JQogICAgdGlkeSgpICU+JQogICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSAlPiUKICAgIGZpbHRlcih0ZXJtICVpbiUgYygnYWNjdXJhY3knLCAnc2Vuc2l0aXZpdHknLCAnc3BlY2lmaWNpdHknLCAncHJlY2lzaW9uJykpICU+JQogICAgbXV0YXRlKGN1dG9mZj1jdXRvZmYpCiAgCn0KCmN1dG9mZnMgPSBzZXEoMC4wMSwwLjk1LDAuMDEpCmxvZ2l0X3ByZWQ9IG1hcF9kZnIoY3V0b2ZmcywgcHJlZGljdGlvbl9tZXRyaWNzKSU+JSBtdXRhdGUodGVybT1hcy5mYWN0b3IodGVybSkpCgpnZ3Bsb3QobG9naXRfcHJlZCwgYWVzKGN1dG9mZixlc3RpbWF0ZSwgZ3JvdXA9dGVybSwgY29sb3I9dGVybSkpICsgZ2VvbV9saW5lKHNpemU9MSkgKwogIHRoZW1lX2J3KCkgKwogIGxhYnModGl0bGU9ICdBY2N1cmFjeSwgU2Vuc2l0aXZpdHksIFNwZWNpZmljaXR5IHkgUHJlY2lzaW9uJywgc3VidGl0bGU9ICdNb2RlbG8gY29tcGxldG8nLCBjb2xvcj0iIikKYGBgCgrCv1F1w6kgcG9kZW1vcyBvYnNlcnZhciBlbiBlbCBncsOhZmljbz8KCsK/UG9kZW1vcyBkZWZpbmlyIHVuIGJ1ZW4gcHVudG8gZGUgY29ydGU/IMK/Q3XDoWwgc2Vyw61hPwoKwr9Qb3IgcXXDqSBsYSBlc3BlY2lmaWNpZGFkIHRpZW5lIGVzZSBjb21wb3J0YW1pZW50bz8KCiMjIyBEYXRhc2V0IGRlIHRlc3RpbmcKClNlbGVjY2lvbmFtb3MgZWwgbW9kZWxvIGNvbXBsZXRvLCB5YSBxdWUgZXMgZWwgcXVlIG1heGltaXphYmEgZWwgcG9yY2VudGFqZSBkZSBkZXZpYW5jZSBleHBsaWNhZGEgeSBlbiBiYXNlIGEgbG8gcXVlIHZpbW9zIGRlZmluaW1vcyB1biBwdW50byBkZSBjb3J0ZSBlbiAwLjI1IChwdWVkZW4gcHJvYmFyIG90cm9zKQoKCmBgYHtyfQpzZWxfY3V0b2ZmID0gMC4yNwojIENyZWFtb3MgZWwgbW9kZWxvCmZ1bGxfbW9kZWwgPC0gZ2xtKGxvZ2l0X2Zvcm11bGFzJGZ1bGwsIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSBkZWZhdWx0KQojIEFncmVnYW1vcyBsYSBwcmVkaWNjaW9uZXMgYWwgZGF0YXNldCBkZSB0ZXN0ZW8KdGFibGU9IGF1Z21lbnQoeD1mdWxsX21vZGVsLCBuZXdkYXRhPXRlc3QsIHR5cGUucHJlZGljdD0ncmVzcG9uc2UnKSAKIyBDbGFzaWZpY2Ftb3MgdXRpbGl6YW1vcyBlbCBwdW50byBkZSBjb3J0ZQp0YWJsZT10YWJsZSAlPiUgCiAgbXV0YXRlKHByZWRpY3RlZF9jbGFzcz1pZl9lbHNlKC5maXR0ZWQ+c2VsX2N1dG9mZiwgMSwgMCkgJT4lIGFzLmZhY3RvcigpLCAKICAgICAgICAgZGVmYXVsdD0gZmFjdG9yKGRlZmF1bHQpKQojIENyZWFtb3MgbGEgbWF0cml6IGRlIGNvbmZ1c2nDs24KY29uZnVzaW9uTWF0cml4KHRhYmxlKHRhYmxlJHByZWRpY3RlZF9jbGFzcywgdGFibGUkZGVmYXVsdCksIHBvc2l0aXZlID0gIjEiKQpgYGAKCiMgU2VndW5kYSBwYXJ0ZQoKIyMgRGVzYmFsYW5jZW8gZGUgbGEgY2xhc2UKCkFsIGV4cGxvcmFyIGVsIGRhdGFzZXQgdmltb3MgcXVlIGV4aXN0w61hIHVuIGZ1ZXJ0ZSBkZXNiYWxhbmNlIGRlIGNsYXNlLiBTw7NsbyBlbCAzJSBkZSBsYXMgb2JzZXJ2YWNpb25lcyBwZXJ0ZW5lY2VuIGEgcGVyc29uYXMgcXVlIGRlZmF1bHRlYXJvbi4gRXN0byBwdWVkZSB0ZW5lciB1biBlZmVjdG8gZW4gbGFzIGVzdGltYWNpb25lcyBkZWwgbW9kZWxvIHkgc3UgY2xhc2lmaWNhY2nDs24gZmluYWwuCgpFeGlzdGVuIGRvcyBtYW5lcmFzIHNlbmNpbGxhcyBjb24gbGFzIGN1YWxlcyBwb2RlbW9zIHRyYWJhamFyIGNvbiB1bmEgY2xhc2UgZGVzYmFsYW5jZWFkYToKCiAgKiBTb2JyZS1tdWVzdHJlbyAob3ZlcnNhbXBsaW5nKSBkZSBsYSBjbGFzZSBtaW5vcml0YXJpYQogIAogICogU3ViLW11ZXN0cmVvICh1bmRlcnNhbXBsaW5nKSBkZSBsYSBjbGFzZSBtYXlvcml0YXJpYQogIApMYSBmdW5jacOzbiBgZ2xtYCBwdWVkZSB0b21hciBjb21vIGFyZ3VtZW50byB1bmEgY29sdW1uYSAoYHdlaWd0aHNgKSBkZSBwb25kZXJhZG9yZXMgcGFyYSBwb2RlciBoYWNlciBlc3RvLiBQb2RlbW9zIGFzaWduYXIgcGVzb3MgbWF5b3JlcyBhIDEgYSBsYSBjbGFzZSBtaW5vcml0YXJpYSAob3ZlcnNhbXBsaW5nKSBvIG1lbm9yZXMgYSAxIGEgbGEgY2xhc2UgbWF5b3JpdGFyaWEgKHVuZGVyc2FtcGxpbmcpLiBFbiBudWVzdHJvIHByb2JsZW1hIHZhbW9zIGEgcmVhbGl6YXIgdW4gc29icmVzYW1wbGVvIGRlIGxhIGNsYXNlIG1pbm9yaXRhcmlhLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiMgQ3JlYW1vcyBsYSBjb2x1bW5hIGRlIHBvbmRlcmFkb3JlcwpkZWZhdWx0IDwtIGRlZmF1bHQgJT4lIG11dGF0ZSh3dD0gaWZfZWxzZShkZWZhdWx0PT0xLDIwLDEpKQojIENyZWFtb3MgbG9zIG1vZGVsb3MgY29uIGxhIGRhdGEgJ2JhbGFuY2VhZGEnCmJhbGFuY2VkX21vZGVscyA8LSBkYXRhX2ZyYW1lKGxvZ2l0X2Zvcm11bGFzKSAlPiUgIyBkYXRhZnJhbWUgYSBwYXJ0aXIgZGVsIG9iamV0byBmb3JtdWxhcwogIG11dGF0ZShtb2RlbHMgPSBuYW1lcyhsb2dpdF9mb3JtdWxhcyksICMgY29sdW1uYSBjb24gbG9zIG5vbWJyZXMgZGUgbGFzIGZvcm11bGFzCiAgICAgICAgIGV4cHJlc3Npb24gPSBwYXN0ZShsb2dpdF9mb3JtdWxhcyksICMgY29sdW1uYSBjb24gbGFzIGV4cHJlc2lvbmVzIGRlIGxhcyBmb3JtdWxhcwogICAgICAgICBtb2QgPSBtYXAobG9naXRfZm9ybXVsYXMsIH5nbG0oLixmYW1pbHkgPSAnYmlub21pYWwnLCBkYXRhID0gZGVmYXVsdCwgd2VpZ2h0cyA9IHd0KSkpICNQYXNhbW9zIGxhIGNvbHVtbmEgd3QgY29tbyBwb25kZXJhZG9yZXMKYGBgCgpWZW1vcyBsYXMgZXN0aW1hY2lvbmVzIGRlIGxvcyBwYXLDoW1ldHJvcyBwYXJhIGVsIG1vZGVsbyBjb21wbGV0by4gwr9FeGlzdGVuIGNhbWJpb3M/CgpgYGB7ciwgIHdhcm5pbmc9RkFMU0UsIGVjaG89RkFMU0V9CmJhbGFuY2VkX21vZGVscyAlPiUgCiAgZmlsdGVyKG1vZGVscyA9PSAiZnVsbCIpICU+JQogIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCx0aWR5KSkgJT4lCiAgdW5uZXN0KHRpZHksIC5kcm9wID0gVFJVRSkgJT4lIAogIG11dGF0ZShlc3RpbWF0ZT1yb3VuZChlc3RpbWF0ZSw1KSwKICAgICAgICAgcC52YWx1ZT1yb3VuZChwLnZhbHVlLDQpKQpgYGAKCkFob3JhIHZlYW1vcyBsYSBldmFsdWFjacOzbiBkZSBsb3MgbW9kZWxvcyDCv1F1w6kgcGFzw7MgY29uIGVsIHBvcmNlbnRhamUgZGUgZGV2aWFuY2UgZXhwbGljYWRhPyDCv1kgY29uIGxhIG51bGE/CgpgYGB7ciwgZWNobz1GQUxTRX0KYmFsYW5jZWRfbW9kZWxzIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgCiAgbXV0YXRlKGdsYW5jZSA9IG1hcChtb2QsZ2xhbmNlKSkKYmFsYW5jZWRfbW9kZWxzICU+JSAKICB1bm5lc3QoZ2xhbmNlLCAuZHJvcCA9IFRSVUUpICU+JQogIG11dGF0ZShwZXJjX2V4cGxhaW5lZF9kZXYgPSAxLWRldmlhbmNlL251bGwuZGV2aWFuY2UpICU+JSAKICBzZWxlY3QoLWMobW9kZWxzLCBkZi5udWxsLCBBSUMsIEJJQykpICU+JSAKICBhcnJhbmdlKGRldmlhbmNlKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpiYWxhbmNlZF9tb2RlbHMgPC0gYmFsYW5jZWRfbW9kZWxzICU+JSAKICBtdXRhdGUocHJlZD0gbWFwKG1vZCxhdWdtZW50LCB0eXBlLnByZWRpY3QgPSAicmVzcG9uc2UiKSkKcHJlZGljdGlvbl9mdWxsIDwtIGJhbGFuY2VkX21vZGVscyAlPiUgCiAgZmlsdGVyKG1vZGVscz09ImZ1bGwiKSAlPiUgCiAgdW5uZXN0KHByZWQsIC5kcm9wPVRSVUUpCnJvY19mdWxsIDwtIHJvYyhyZXNwb25zZT1wcmVkaWN0aW9uX2Z1bGwkZGVmYXVsdCwgcHJlZGljdG9yPXByZWRpY3Rpb25fZnVsbCQuZml0dGVkKQpwcmVkaWN0aW9uX2JhZCA8LSBiYWxhbmNlZF9tb2RlbHMgJT4lIAogIGZpbHRlcihtb2RlbHM9PSJzdHVkX2luYyIpICU+JSAKICB1bm5lc3QocHJlZCwgLmRyb3A9VFJVRSkKcm9jX2JhZCA8LSByb2MocmVzcG9uc2U9cHJlZGljdGlvbl9iYWQkZGVmYXVsdCwgcHJlZGljdG9yPXByZWRpY3Rpb25fYmFkJC5maXR0ZWQpCmBgYAoKIyMjIFZpb2xpbiBwbG90cywgQ3VydmFzIFJPQyB5IEFVQ3MKClJlYWxpemFtb3MgbG9zIGdyw6FmaWNvcyBkZSB2aW9saW4sIGxhcyBjdXJ2YXMgUk9DIHkgY2FsY3VsYW1vcyBsYXMgQVVDLgoKYGBge3IsIHdhcm5pbmc9RiwgZWNobz1GQUxTRX0KCnZpb2xpbl9mdWxsID0gZ2dwbG90KHByZWRpY3Rpb25fZnVsbCwgYWVzKHg9ZGVmYXVsdCwgeT0uZml0dGVkLCBncm91cD1kZWZhdWx0LGZpbGw9ZmFjdG9yKGRlZmF1bHQpKSkgKyAKICBnZW9tX3Zpb2xpbigpICsKICB0aGVtZV9idygpICsKICBndWlkZXMoZmlsbD1GQUxTRSkgKwogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyBjb21wbGV0bycsIHk9J1ByZWRpY3RlZCBwcm9iYWJpbGl0eScpCgp2aW9saW5fYmFkPWdncGxvdChwcmVkaWN0aW9uX2JhZCwgYWVzKHg9ZGVmYXVsdCwgeT0uZml0dGVkLCBncm91cD1kZWZhdWx0LCBmaWxsPWZhY3RvcihkZWZhdWx0KSkpICsgCiAgZ2VvbV92aW9saW4oKSArIAogIHRoZW1lX2J3KCkgKwogIGd1aWRlcyhmaWxsPUZBTFNFKSArCiAgbGFicyh0aXRsZT0nVmlvbGluIHBsb3QnLCBzdWJ0aXRsZT0nTW9kZWxvIG1hbG8nLCB5PSdQcmVkaWN0ZWQgcHJvYmFiaWxpdHknKQoKcGxvdF9ncmlkKHZpb2xpbl9iYWQsIHZpb2xpbl9mdWxsKQoKZ2dyb2MobGlzdChmdWxsPXJvY19mdWxsLCBiYWQ9cm9jX2JhZCksIHNpemU9MSkgKyBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSdkYXNoZWQnKSArIHRoZW1lX2J3KCkgKyBsYWJzKHRpdGxlPSdDdXJ2YXMgUk9DJywgY29sb3I9J01vZGVsbycpCgpwcmludChwYXN0ZSgnQVVDIE1vZGVsbyBjb21wbGV0bzonLCByb3VuZChyb2NfZnVsbCRhdWMsMykpKQoKcHJpbnQocGFzdGUoJ0FVQyBNb2RlbG8gbWFsbzonLCByb3VuZChyb2NfYmFkJGF1YywzKSkpCgpgYGAKCj4gwr9Ew7NuZGUgc2UgdmVuIGxvcyBjYW1iaW9zIG3DoXMgbm90b3Jpb3MgcmVzcGVjdG8gYSBudWVzdHJvcyBtb2RlbG9zIGFudGVyaW9yZXMgcXVlIG5vIHRlbsOtYW4gZW4gY3VlbnRhIGVsIGRlc2JhbGFuY2UgZGUgbGEgY2xhc2U/CgojIyMgUHVudG8gZGUgY29ydGUKClZvbHZlbW9zIGEgcmVhbGl6YXIgbGFzIHBydWViYXMgcGFyYSB2YXJpb3MgcHVudG9zIGRlIGNvcnRlIHkgZ3JhZmljYW1vcyBlbCBhY2N1cmFjeSwgbGEgc2Vuc2liaWxpZGFkLCBsYSBlc3BlY2lmaWNpZGFkLCBlbCByZWNhbGwgeSBsYSBwcmVjaXNpb24gcGFyYSBjYWRhIHVubyBkZSBlbGxvcy4KCmBgYHtyLCBlY2hvPUZBTFNFfQpjdXRvZmZzID0gc2VxKDAuMDEsMC45OSwwLjAxKQpsb2dpdF9wcmVkPSBtYXBfZGZyKGN1dG9mZnMsIHByZWRpY3Rpb25fbWV0cmljcyklPiUgbXV0YXRlKHRlcm09YXMuZmFjdG9yKHRlcm0pKQoKZ2dwbG90KGxvZ2l0X3ByZWQsIGFlcyhjdXRvZmYsZXN0aW1hdGUsIGdyb3VwPXRlcm0sIGNvbG9yPXRlcm0pKSArIGdlb21fbGluZShzaXplPTEpICsKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlPSAnQWNjdXJhY3ksIFByZWNpc2lvbiwgU2Vuc2l0aXZpdHkgeSBTcGVjaWZpY2l0eScsIHN1YnRpdGxlPSAnTW9kZWxvIGNvbXBsZXRvJywgY29sb3I9IiIpCmBgYAoKwr9RdcOpIGNhbWJpb3MgdmVtb3MgcmVzcGVjdG8gYWwgZ3LDoWZpY28gYW50ZXJpb3I/CgojIyMgRGF0YXNldCBkZSB0ZXN0aW5nCgpQcm9iYW1vcyBlbiBlbCBkYXRhc2V0IGRlIHRlc3RpbmcgbnVlc3RybyBtb2RlbG8gYmFsYW5jZWFkby4gTm8gZXMgbmVjZXNhcmlvIHF1ZSBsZSBjcmVlbW9zIHBlc29zIGFsIGRhdGFzZXQgZGUgdGVzdGVvLgoKYGBge3IsIGVjaG89RkFMU0V9CmZ1bGxfbW9kZWwgPC0gZ2xtKGxvZ2l0X2Zvcm11bGFzJGZ1bGwsIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSBkZWZhdWx0LCB3ZWlnaHRzID0gd3QpCgp0YWJsZT0gYXVnbWVudCh4PWZ1bGxfbW9kZWwsIG5ld2RhdGE9dGVzdCwgdHlwZS5wcmVkaWN0PSdyZXNwb25zZScpIAoKdGFibGU9dGFibGUgJT4lIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPjAuOTEsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwKICAgICAgICAgICBkZWZhdWx0PSBmYWN0b3IoZGVmYXVsdCkpCgpjb25mdXNpb25NYXRyaXgodGFibGUodGFibGUkcHJlZGljdGVkX2NsYXNzLCB0YWJsZSRkZWZhdWx0KSwgcG9zaXRpdmUgPSAiMSIpCmBgYAoKCg==